package com.danbay.framework.springnc.feign;

import com.danbay.framework.springnc.annotation.RequestMapping;
import feign.*;
import org.springframework.cloud.netflix.feign.AnnotatedParameterProcessor;
import org.springframework.cloud.netflix.feign.annotation.PathVariableParameterProcessor;
import org.springframework.cloud.netflix.feign.annotation.RequestHeaderParameterProcessor;
import org.springframework.cloud.netflix.feign.annotation.RequestParamParameterProcessor;
import org.springframework.cloud.netflix.feign.support.SpringMvcContract;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.*;

/**
 * @author SJG
 *         2017/12/20.
 */
public class NoControllerContract extends SpringMvcContract {
    private static final String ACCEPT = "Accept";
    private static final String CONTENT_TYPE = "Content-Type";
    private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
    private final Map<Class<? extends Annotation>, AnnotatedParameterProcessor> annotatedArgumentProcessors;
    private final Map<String, Method> processedMethods;
    private final ConversionService conversionService;
    private final Param.Expander expander;
    private ResourceLoader resourceLoader;

    public NoControllerContract() {
        this(Collections.emptyList());
    }

    public NoControllerContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors) {
        this(annotatedParameterProcessors, new DefaultConversionService());
    }

    public NoControllerContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors, ConversionService conversionService) {
        this.processedMethods = new HashMap();
        this.resourceLoader = new DefaultResourceLoader();
        Assert.notNull(annotatedParameterProcessors, "Parameter processors can not be null.");
        Assert.notNull(conversionService, "ConversionService can not be null.");
        Object processors;
        if (!annotatedParameterProcessors.isEmpty()) {
            processors = new ArrayList(annotatedParameterProcessors);
        } else {
            processors = this.getDefaultAnnotatedArgumentsProcessors();
        }

        this.annotatedArgumentProcessors = this.toAnnotatedArgumentProcessorMap((List) processors);
        this.conversionService = conversionService;
        this.expander = new SpringMvcContract.ConvertingExpander(conversionService);
    }


    @Override
    protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
        if (clz.getInterfaces().length == 0) {
            RequestMapping classAnnotation = AnnotatedElementUtils.findMergedAnnotation(clz, RequestMapping.class);
            if (classAnnotation != null && classAnnotation.value().length > 0) {
                String pathValue = Util.emptyToNull(classAnnotation.value()[0]);
                pathValue = this.resolve(pathValue);
                if (!pathValue.startsWith("/")) {
                    pathValue = "/" + pathValue;
                }

                data.template().insert(0, pathValue);
            }
        }

    }

    @Override
    protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
        if (RequestMapping.class.isInstance(methodAnnotation) || methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {
            RequestMapping methodMapping = (RequestMapping) AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
            RequestMethod[] methods = methodMapping.method();
            if (methods.length == 0) {
                methods = new RequestMethod[]{RequestMethod.GET};
            }

            this.checkOne(method, methods, "method");
            data.template().method(methods[0].name());
            this.checkAtMostOne(method, methodMapping.value(), "value");
            if (methodMapping.value().length > 0) {
                String pathValue = Util.emptyToNull(methodMapping.value()[0]);
                if (pathValue != null) {
                    pathValue = this.resolve(pathValue);
                    if (!pathValue.startsWith("/") && !data.template().toString().endsWith("/")) {
                        pathValue = "/" + pathValue;
                    }

                    data.template().append(pathValue);
                }
            }

            this.parseProduces(data, method, methodMapping);
            this.parseConsumes(data, method, methodMapping);
            this.parseHeaders(data, method, methodMapping);
            data.indexToExpander(new LinkedHashMap());
        }
    }

    private String resolve(String value) {
        return StringUtils.hasText(value) && this.resourceLoader instanceof ConfigurableApplicationContext ? ((ConfigurableApplicationContext) this.resourceLoader).getEnvironment().resolvePlaceholders(value) : value;
    }

    private void checkAtMostOne(Method method, Object[] values, String fieldName) {
        Util.checkState(values != null && (values.length == 0 || values.length == 1), "Method %s can only contain at most 1 %s field. Found: %s", new Object[]{method.getName(), fieldName, values == null ? null : Arrays.asList(values)});
    }

    private void checkOne(Method method, Object[] values, String fieldName) {
        Util.checkState(values != null && values.length == 1, "Method %s can only contain 1 %s field. Found: %s", new Object[]{method.getName(), fieldName, values == null ? null : Arrays.asList(values)});
    }

    private void parseProduces(MethodMetadata md, Method method, RequestMapping annotation) {
        this.checkAtMostOne(method, annotation.produces(), "produces");
        String[] serverProduces = annotation.produces();
        String clientAccepts = serverProduces.length == 0 ? null : Util.emptyToNull(serverProduces[0]);
        if (clientAccepts != null) {
            md.template().header("Accept", new String[]{clientAccepts});
        }

    }

    private void parseConsumes(MethodMetadata md, Method method, RequestMapping annotation) {
        this.checkAtMostOne(method, annotation.consumes(), "consumes");
        String[] serverConsumes = annotation.consumes();
        String clientProduces = serverConsumes.length == 0 ? null : Util.emptyToNull(serverConsumes[0]);
        if (clientProduces != null) {
            md.template().header("Content-Type", new String[]{clientProduces});
        }

    }

    private void parseHeaders(MethodMetadata md, Method method, RequestMapping annotation) {
        if (annotation.headers() != null && annotation.headers().length > 0) {
            String[] var4 = annotation.headers();
            int var5 = var4.length;

            for (int var6 = 0; var6 < var5; ++var6) {
                String header = var4[var6];
                int index = header.indexOf(61);
                md.template().header(this.resolve(header.substring(0, index)), new String[]{this.resolve(header.substring(index + 1).trim())});
            }
        }

    }

    private List<AnnotatedParameterProcessor> getDefaultAnnotatedArgumentsProcessors() {
        List<AnnotatedParameterProcessor> annotatedArgumentResolvers = new ArrayList();
        annotatedArgumentResolvers.add(new PathVariableParameterProcessor());
        annotatedArgumentResolvers.add(new RequestParamParameterProcessor());
        annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor());
        return annotatedArgumentResolvers;
    }

    private Map<Class<? extends Annotation>, AnnotatedParameterProcessor> toAnnotatedArgumentProcessorMap(List<AnnotatedParameterProcessor> processors) {
        Map<Class<? extends Annotation>, AnnotatedParameterProcessor> result = new HashMap();
        Iterator var3 = processors.iterator();

        while(var3.hasNext()) {
            AnnotatedParameterProcessor processor = (AnnotatedParameterProcessor)var3.next();
            result.put(processor.getAnnotationType(), processor);
        }

        return result;
    }
}
